Repository navigation
fix(mcp): validate_expression takes the flow's declared variables at a flow condition - #33255
Conversation
…a flow condition The probe flow declares the variables the caller hands it, so the runtime flow door's shadowed-read warning fires, and the shared validator is handed their names as `flowVariables`, so a bare read of one is not typed as the field. Without the input the answer is unchanged. Claude-Session: https://claude.ai/code/session_017xRBcSZ51q2s3nMfakhdJ3 Co-authored-by: Claude <noreply@anthropic.com>
…t the runtime flow door The shadowed-read warning is now a `pass` in the flow-condition table, read through the entry at the start, decision and edge slots with the variables declared; the tool-level pins cover the type verdict moving to the variable, the unchanged CONTROL answer without the input, the off-site refusal, a variable that shadows nothing, the spec's own variable refusals, and one stdio/HTTP parity pin. Adds the changeset. Claude-Session: https://claude.ai/code/session_017xRBcSZ51q2s3nMfakhdJ3 Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check10 anchor(s) derived from 2 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 15 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 613c15ade235b26fde7bfb47408521b67e98b7ab && git checkout 613c15ade235b26fde7bfb47408521b67e98b7ab
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin e540baf8873c22642ce191b99e7e5ce1fd3b4620 4cae1f45ab2246d254a51847b7bdd1e8a0752266 && git checkout -B drift-repro e540baf8873c22642ce191b99e7e5ce1fd3b4620 && git merge --no-ff 4cae1f45ab2246d254a51847b7bdd1e8a0752266
node scripts/docs-audit/affected-docs.mjs --json e540baf8873c22642ce191b99e7e5ce1fd3b4620 |
|
review#2 · os-project-manager · session_01FEkQ5nhu4dRbKVQKDB4CDB Contract-review-claim: session_01FEkQ5nhu4dRbKVQKDB4CDB · head 4cae1f4 · 2026-11-14T15:09Z |
|
review#2 · os-project-manager · session_01FEkQ5nhu4dRbKVQKDB4CDB Contract reviewServed-tier: ① Derived judgmentsGate state on this head when it was read: 69 check-runs. 31 succeeded, 15 were skipped, 0 failed and 23 were still in progress (Test Core 8 of 8 shards, Test UI 14 shards, Type Check UI). Every Lint family had passed, and so had Check Changeset, PR-body guards, Governed Surface Queue Guard, Dogfood Regression Gate, Build Core and Type Check · workspace. This verdict judges the diff; the in-progress runs still have to finish green before the PR lands. Accept-set and public-surface changes the diff makes:
Findings:
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS |
|
domain:server#7 · sunhaolin · session_017xRBcSZ51q2s3nMfakhdJ3 席位核对 · 2026-11-14T15:31Z · ACCEPTHead
State change: ready and auto-merge armed ( |
Fixes #33176
Clause-②: yes (widening) —
validate_expressiongains an optional input,variables, carrying the flow's declared variables atsite: 'flow_condition', so the tool accepts a call it refuses today (strictToolInput); with no variables the answer is unchanged.Metadata-protocol: none
Skills-docs: follow-up #33272
维护者速读
validate_expression在flow_condition位点新增可选入参variables(流程声明的变量,形状即 spec 的FlowVariableSchema)。给了它,探针流程声明这些变量,运行时流程门禁自己的"变量遮蔽同名字段"告警会回来;共享校验器把这些裸名当变量,不再按字段判类型。packages/lint/src/authored-site-expression.ts(authoredFlowProbe声明变量)、packages/mcp/src/mcp-http-tools.ts(variables入参、非流程位点拒收、flowVariables提示、gatePassFindings传参)。pnpm --filter @objectstack/mcp exec vitest run src/mcp-validate-expression.test.ts,预期 77 个测试全过,其中title * 2 > 10带variables时无类型错误且得到门禁的遮蔽告警,不带时报type-mismatch-run-fails。What changes
Input shape: an optional
variablesinput, not "read from a named served flow". I chose the input. At a flow condition the tool reads only the subject object's served definition. Reading a named flow would need a new bridge member on both doors (the runtime bridge and the stdio bridge), a permission fence for flow metadata, and an answer for a flow that is not saved yet, which is the usual case while an agent authors one. That is outside this claim's file surface. The input takes the flow's own shape,z.array(FlowVariableSchema)from@objectstack/spec/automation, so an agent can hand over a flow'svariablesarray as it stands. A malformed variable gets the spec's own refusal (a missingtype, a$name, an alias key such asinput). No new shape is written by hand, which follows theAggregationNodeSchemaprecedent onaggregate_records.With
variablesatsite: 'flow_condition':gatePassFindingshands the variables tojudgeAuthoredSiteExpression, andauthoredFlowProbedeclares them on the one-flow probe. The gate's own walk then runswarnShadowedFieldReadson the probe's edge and gives its warning for a variable named like a field ofobjectNamethat is read bare. ⛔ Nothing is re-implemented: the probe only declares what the flow declares.ExprSchemaHint.flowVariables, the key the gate hands it on the same flow. A bare read of a declared variable is therefore not typed as the field. "Typed as the variable" means what it means at the gate: the bare name staysdyn. Neither the gate nor the validator types a variable by its declaredtype, so the tool does not either.record.FIELDstill reads the field and is judged as before.Without
variablesnothing changes. The probe carries novariableskey and the hint carries noflowVariableskey, so every call answers byte for byte as before. The tool description and the input description say that the shadowed-read warning needs the variables.variableson any other site is refused, asstartConditionis:`variables` describes a flow; it applies only with site "flow_condition".stdio. Measured: both transports register one tool definition through
wireBridgeTools, and the stdio bridge already servesservedObjectDefinitionOf. Sostdio-data-bridge.tsneeds no change. A parity pin drives a realStdioServerTransportand the HTTP door with the same arguments.Measured against the dispatch's mechanism notes (on
origin/main295ce127):objectName, expression, site, severity, triggerKind, sweepObject, startCondition, field. Holds.flowVariables, names only. It does not type a variable at all, so a bare variable is left untyped rather than typed by the variable'stype.Tests
packages/lint/src/authored-site-expression.test.ts(50 → 57)warnShadowedFieldReadsmoves frompass-silent-heretopass. Its fixture now carriesvariables, and the entry is pinned equal to the runtime gate at the start,decisionand edge slots of a hand-written flow that declares the same variables (+2 slot cases).with the flow's declared variables (#33176)(+5):variableskey without them;record.titleis not;packages/mcp/src/mcp-validate-expression.test.ts(72 → 77)titleshadows the text fieldtitle, read bare intitle * 2 > 10. The tool's whole verdict, validator half included, equals the gate's verdict on a flow declaring it: one shadowed-read warning, no errors,ok: true. The finding namestitleandgp_req.variables:ok: falsewithtype-mismatch-run-fails, the bare name typed as the field, equal to the definition-less bridge's answer.startConditiontoo.threshold) gets[true, [], []], as at the gate.type,$record, the aliasinput.variablesatvalidation, attemplateand at the default site.packages/mcp/src/mcp-stdio-tools.test.ts(18 → 20): the samevariablesinput schema is advertised on stdio and HTTP. The shadowed call, the CONTROL call and the off-site refusal each give the sametools/callresult on both transports, and the three answers differ as the input says they should.Acceptance notes
screenfield names and the engine's own names (record,previous,vars,current_user) untyped. The tool is handed what the flow declares undervariables, so a bare name that one of those others holds is still typed as the field here. The gate's shadowed-read warning reads declared variables only, so that warning is exact.typeis required by the spec's shape and read by neither half, the same as at the gate.ruling|Ruling-ref|maintainer|⛔|#33147|#32872) in the touched functions:authored-site-expression.ts: one binding line, "⛔ Nothing here re-implements a pass", which this change keeps. Every other hit is a provenance tag.validate_expressionhandler andgatePassFindingsinmcp-http-tools.ts: no ruling, maintainer or ⛔ line, and 10 provenance tags.9d07ffa0. It touchesauthored-site-expression.test.tsat lines 103–153 (walkPushes,CYCLE) andvalidate-expressions.ts. This PR touches that test file only at lines 455–780 and does not touchvalidate-expressions.ts, so no hunk is shared.authored-site-expression.tssaid the shadowed-read warning never fires through the entry. It now says the warning fires when the variables are given, and does not fire, as before, when they are not.Verification
Head
4cae1f45ab(origin/main295ce127plus two commits). Every reading below was taken at that head.Package suites, typecheck and lint
Ablations (each through scripts/ablation-replace.mjs; each restored with blob == HEAD and git diff HEAD empty)
The MCP suites read
@objectstack/lintthrough itsdist/(no vitest alias). L1 was read on the lint suite, which importssrc. The MCP leg of the probe is covered by A2.Derived gate families (dispatch-gates --commands, 67 families; --ran reconciliation)
test:repoand the full farm are CI's. No export was renamed or removed, socheck-platform-checklistis not owed. Full dogfood is not owed: the title has no!and the Clause-② arm is(widening).Generated by Claude Code